﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Reflection;
using BestCode.Creds.Data.Mappings;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Criterion;
using NHibernate.Dialect;
using NHibernate.Driver;
using NHibernate.Impl;
using NHibernate.Mapping.ByCode;
using Configuration = NHibernate.Cfg.Configuration;

namespace BestCode.Creds.Data.MapHelper
{
    public class DaoBase<T> : IDataAccessBase<T> where T : class
    {
         #region Thread-safe, lazy Singleton

        static ISession instance = null;
        static readonly object padlock = new object();

        public static ISession Session
        {
            get
            {
                lock (padlock)
                {
                    if (instance == null)
                    {
                        instance = SessionFactoryBuilder.BuildSessionFactory().OpenSession();
                    }
                    return instance;
                }
            }
        }

        #endregion

        //~DaoBase()
        //{
        //    Session.Close();
        //}

        public T GetById(object id)
        {
            ICriteria criteria = Session.CreateCriteria(typeof(T));
            var result = criteria.Add(Expression.Eq("Id", id));
            return result.UniqueResult<T>();
        }

        public T Insert(T entity)
        {
            using (var tx = Session.BeginTransaction())
            {
                try
                {
                    Session.Save(entity);
                   // Session.Flush();
                    tx.Commit();
                    return entity;
                }
                catch (Exception ex)
                {
                    tx.Rollback();

                    RemoveFromContext(entity);

                    throw new Exception((ex.InnerException != null) ? ex.InnerException.Message : ex.Message);
                }
               
            }
        }

        public T Save(T entity)
        {
            using (var tx = Session.BeginTransaction())
            {
                try
                {
                    Session.SaveOrUpdate(entity);
                    Session.Flush();
                    tx.Commit();
                    return entity;
                }
                catch (Exception ex)
                {
                    tx.Rollback();
                    RemoveFromContext(entity);
                    throw new Exception((ex.InnerException != null) ? ex.InnerException.Message : ex.Message);
                }
            }
        }

        public T Update(T entity)
        {
            using (var tx = Session.BeginTransaction())
            {
                try
                {
                    Session.Update(entity);
                    //Session.Flush();
                    tx.Commit();
                    return entity;
                }
                catch (Exception ex)
                {
                    tx.Rollback();
                    RemoveFromContext(entity);
                    throw new Exception((ex.InnerException != null) ? ex.InnerException.Message : ex.Message);
                }
            }
        }


        public void Delete(T entity)
        {
            using (var tx = Session.BeginTransaction())
            {
                try
                {
                    Session.Delete(entity);
                    Session.Flush();
                    tx.Commit();
                }
                catch (Exception ex)
                {
                    tx.Rollback();
                    RemoveFromContext(entity);
                    throw new Exception((ex.InnerException != null) ? ex.InnerException.Message : ex.Message);
                }
            }
        }

        public IList<T> GetAll()
        {
            var res = Session.CreateCriteria(typeof(T));
            return res.List<T>();
        }

        public void CommitChanges()
        {
            ITransaction transaction = Session.BeginTransaction();

            try
            {
                transaction.Commit();
            }
            catch (HibernateException ex)
            {
                transaction.Rollback();
                //close and dispose session here
                throw ex;
            }
            finally
            {
                transaction.Dispose();
            }
        }

        public void Detach(T entity)
        {
            Session.Evict(entity);
        }

        private void RemoveFromContext(T entity)
        {
            if (Session.GetIdentifier(entity) == null)
                ((SessionImpl)Session).PersistenceContext.EntityEntries.Remove(entity);
        }

    }



    public class SessionFactoryBuilder
    {
        public static ISessionFactory BuildSessionFactory()
        {
            var nhConfiguration = ConfigureNHibernate();
            var mapping = GetMappings();
            
            nhConfiguration.AddDeserializedMapping(mapping, "MappNameCreds");
            return nhConfiguration.BuildSessionFactory();
        }

        private static Configuration ConfigureNHibernate()
        {
            var configure = new Configuration();
            configure.SessionFactoryName("BuildIt");

            configure.DataBaseIntegration(db =>
            {
                db.Dialect<MsSql2008Dialect>();
                db.Driver<SqlClientDriver>();
                db.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
                db.IsolationLevel = IsolationLevel.ReadCommitted;
                db.ConnectionString = ConfigurationManager.ConnectionStrings["CREDS"].ConnectionString;
                db.Timeout = 10;

                // enabled for testing
                //db.LogFormatedSql = true;
                db.LogSqlInConsole = true;
                //db.AutoCommentSql = true;
            });

            return configure;
        }

        protected static HbmMapping GetMappings()
        {
            //There is a dynamic way to do this, but for simplicity I chose to hard code
            var mapper = new ModelMapper();

            //mapper.AddMapping<EntityMapping>();
            //mapper.AddMapping<TemplateMapping>();
            //var mapping = mapper.CompileMappingFor(new[] { typeof(EntityMapping), typeof(TemplateMapping) });

           

            var ass = Assembly.GetAssembly(typeof (EntityMapping)).GetExportedTypes();
            mapper.AddMappings(ass);
            var mapping = mapper.CompileMappingForAllExplicitlyAddedEntities();


            return mapping;
        }

    }
}
